/*
 * Copyright (C) 2019-2020 Yaong <yaongtime@gmail.com>
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "vc4_private.h"
#include "vc4_vk_job.h"

#include <xf86drm.h>
#include "vc4_drm.h"

#include "vk_util.h"

static VkResult
process_fence_to_signal(struct vc4_device *device, VkFence _fence)
{
    if (_fence == VK_NULL_HANDLE)
        return VK_SUCCESS;

    struct vc4_fence *fence = vc4_fence_from_handle(_fence);

    if (fence->fd >= 0)
        close(fence->fd);
    fence->fd = -1;

    int fd;
    mtx_lock(&device->mutex);
    drmSyncobjExportSyncFile(device->fd, device->last_job_sync, &fd);
    mtx_unlock(&device->mutex);
    if (fd == -1)
        return vk_error(device->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

    int ret = drmSyncobjImportSyncFile(device->fd, fence->sync, fd);
    if (ret)
        return vk_error(device->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

    fence->fd = fd;

    return VK_SUCCESS;
}

static VkResult
queue_submit_cmd_buffer_batch(struct vc4_queue *queue,
                              const VkSubmitInfo *pSubmit)
{
    struct vc4_semaphore *sem;

    //last in-fence wait on the driver
    if (pSubmit->waitSemaphoreCount > 1) {
        queue->sem = vc4_semaphore_from_handle(pSubmit->pWaitSemaphores[pSubmit->waitSemaphoreCount - 1]);
    }

    for(uint32_t i = 0; i < pSubmit->commandBufferCount; i++) {
        struct vc4_cmd_buffer *cmd_buffer = vc4_cmd_buffer_from_handle(pSubmit->pCommandBuffers[i]);

        //out fence
        if (pSubmit->signalSemaphoreCount >= (i + 1)) {
            sem = vc4_semaphore_from_handle(pSubmit->pSignalSemaphores[i]);
            queue->job_syncobj = sem->sync;
        } else {
            sem = NULL;
            queue->job_syncobj = queue->device->last_job_sync;
        }

        list_for_each_entry_safe(struct vc4_vk_job, job, &cmd_buffer->state.render_jobs, list) {
            vc4_vk_job_submit(queue, job);
            //only wait on frist submit
            queue->sem = NULL;
            //export out fence
            if (pSubmit->signalSemaphoreCount >= i)
                drmSyncobjExportSyncFile(queue->device->fd, queue->job_syncobj, &sem->fd);

            // list_del(&job->list);
            // list_addtail(&job->list, &cmd_buffer->state.render_jobs_done);
        }
    }

    if (sem)
        drmSyncobjImportSyncFile(queue->device->fd, queue->device->last_job_sync, sem->fd);

    return VK_SUCCESS;
}

VkResult
vc4_QueueSubmit(VkQueue _queue,
                 uint32_t submitCount,
                 const VkSubmitInfo* pSubmits,
                 VkFence fence)
{
    VC4_FROM_HANDLE(vc4_queue, queue, _queue);
    // VC4_FROM_HANDLE(vc4_cmd_buffer, cmd_buffer, pSubmits[0].pCommandBuffers[0]);
    VkResult result = VK_SUCCESS;

    for (uint32_t i = 0; i < submitCount; i++) {

        const struct wsi_memory_signal_submit_info *mem_signal_info =
            vk_find_struct_const(pSubmits[i].pNext,
                                 WSI_MEMORY_SIGNAL_SUBMIT_INFO_MESA);

        uint32_t sem_count = mem_signal_info ? pSubmits->waitSemaphoreCount :
                                               pSubmits->waitSemaphoreCount > 0 ? pSubmits->waitSemaphoreCount - 1 : 0;
        if (sem_count > 0) {

            uint32_t *syncobjs = vk_zalloc(&queue->device->vk.alloc,
                                           sizeof(*syncobjs) * sem_count, 8,
                                           VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
            if (!syncobjs)
                return vk_error(queue->device->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

            for (int j = 0; j < sem_count; j++) {
                struct vc4_semaphore *sem = vc4_semaphore_from_handle(pSubmits->pWaitSemaphores[j]);
                syncobjs[j] = sem->sync;
            }

            unsigned flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT |
                             DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL;

            int ret = drmSyncobjWait(queue->device->fd,
                                     syncobjs, sem_count,
                                     INT64_MAX, flags, NULL);

            vk_free(&queue->device->vk.alloc, syncobjs);
            if (ret)
                return vk_error(queue->device->instance, VK_ERROR_DEVICE_LOST);
        }

        if (mem_signal_info) {
            printf("%s %d not implement !\n", __FUNCTION__, __LINE__);
            continue;
        }

        result = queue_submit_cmd_buffer_batch(queue, &pSubmits[i]);
        if (result != VK_SUCCESS && result != VK_NOT_READY)
            goto done;
    }

    result = process_fence_to_signal(queue->device, fence);

done:
    return result;
}